function _CloneStat(stat) {
    return JSON.parse(JSON.stringify(stat));
}

function _GetRandomElement(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
}

function _GetUniqueName(prefix, nextIndex) {
    let n = nextIndex ?? 0;
    return _ => {
        return `${prefix}-cursor-${n++}`;
    }
}
function _GetRandomName(prefix, min, max) {
    return `${prefix}-cursor-${Math.randomInt(0, max)}`;
}

const IndexedDBFactory = self.indexedDB || self.webkitGlobalDB || self.mozGlobalDB || self.msGlobalDB;

function _DeleteAllDB(DBCount) {
    const _GetDBName = _GetUniqueName("DB");
    for (let i = 0; i < DBCount; ++i) {
        IndexedDBFactory.deleteDatabase(_GetDBName());
    }
}

function _OpenDB(DBName) {
    return new Promise((resolve, reject) => {
        const _dbOpenRequester = IndexedDBFactory.open(DBName);
        _dbOpenRequester.onsuccess = () => {
            let _db = _dbOpenRequester.result;
            resolve(_db);
        }
        _dbOpenRequester.onerror = evt => {
            console.error(`[IndexedDB tester] can NOT open IndexedDB. reason:\n${evt}`);
            reject();
        }
    });
}
function _SetData(DB, tableName, data) {
    return new Promise((resolve, reject) => {
        const _req = DB.transaction(tableName, 'readwrite');
        _req.objectStore(tableName)
            .put(data)
            .onsuccess = _ => {
                resolve();
            };
        _req.onerror = _ => reject();
    });
}
function _GetDatas(DB, tableName, recordNames) {
    const _arr = [];
    const _req = DB.transaction(tableName, 'readonly');
    _req.objectStore(tableName).openCursor().onsuccess = evt => {
        const cursor = evt.target.result;
        if (cursor) {
            listItem.textContent = `${cursor.value.albumTitle}, ${cursor.value.year}`;

            cursor.continue();
        } else {
        }
    }
    for (let i = 0, N = recordNames.length; i < N; ++i) {
        _arr.push(new Promise((resolve, reject) => {
            let _r = _os.get(recordNames[i])
                .onsuccess = evt => {
                    resolve(evt.target.result)
                };//.onsuccess = () => { }
            _r.onerror = reject;
        }))
    }
    return Promise.all(_arr);
}

function _GetDatas_v2(DB, tableName, recordNames) {
    console.count("transaction");
    const _req = DB.transaction(tableName, 'readonly');
    const _os = _req.objectStore(tableName).index('key');
    return new Promise((resolve, reject) => {
        const _data = [];
        let N = recordNames.length;
        for (let i = 0; i < N; ++i) {
            _os.get(recordNames[i])
                .onsuccess = evt => {
                    _data.push(evt.target.result)
                    if (--N <= 0) {
                        resolve(_data);
                    }
                };//.onsuccess = () => { }
        }
        _req.onerror = _ => reject();
    });
}


const _INIT_STAT_ = Object.freeze({
    DBCount: 0,
    TableCount: 0,
    RecordCount: 0,
    DataSizeInBytes: 0,
});

export default function IndexedDBCase() {
    const _CASE_NAME_ = "with index";
    let _stat = JSON.parse(localStorage.getItem(_CASE_NAME_) ?? JSON.stringify(_INIT_STAT_));
    let _fakeData = undefined;
    let _arrDB = [];

    async function CreateXDBYTableZData(x, y, z, sizeInBytes) {
        _stat.DBCount = x;
        _stat.TableCount = y;
        _stat.RecordCount = z;
        _stat.DataSizeInBytes = sizeInBytes;
        _fakeData = new Float32Array(parseInt(sizeInBytes / 4));

        const _GetDBName = _GetUniqueName("DB");
        let _arr = [];
        while (--x >= 0) {
            _arr.push(_CreateDBWithXTable(_GetDBName(), y));
        }

        const _arrDB = await Promise.all(_arr);
        _arr.length = 0;
        _arrDB.forEach(DB => {
            _arr.push(_SetAllTableXData(DB, 0, z));
        });

        await Promise.all(_arr);
        localStorage.setItem(_CASE_NAME_, JSON.stringify(_stat));
        return _arrDB;
    }

    function _CreateDBWithXTable(DBName, x) {
        return new Promise((resolve, reject) => {
            const _GetTableName = _GetUniqueName("TBL");
            const _req = IndexedDBFactory.open(DBName);
            _req.onsuccess = () => {
                let _db = _req.result;
                resolve(_db);
            }
            _req.onupgradeneeded = evt => {
                let _db = _req.result;
                while (--x >= 0) {
                    const _os = _db.createObjectStore(_GetTableName(), { keyPath: 'url' });
                    _os.createIndex('key', 'key', { unique: true });
                }
            }
            _req.onerror = evt => {
                console.error(`[IndexedDB tester] can NOT open IndexedDB. reason:\n${evt}`);
                reject();
            }
        });
    }
    async function _SetAllTableXData(DB, nextIndex, x) {
        const _arr = [];
        const _tableNames = [...DB.objectStoreNames];
        _tableNames.forEach(name => {
            _arr.push(_SetTableXData(DB, name, nextIndex, x));
        });
        return Promise.all(_arr);
    }

    async function _SetTableXData(DB, tableName, nextIndex, x) {
        const _GetKeyName = _GetUniqueName("K", nextIndex);
        const _arr = [];
        while (--x >= 0) {
            const _recordName = _GetKeyName();
            _arr.push(_SetData(DB, tableName, { data: _fakeData, url: _recordName, key: _recordName }));
        };
        return Promise.all(_arr);
    }

    async function Open() {
        if (_arrDB.length <= 0) {
            const _GetDBName = _GetUniqueName("DB");
            let _arr = [];
            let n = _stat.DBCount;
            while (--n >= 0) {
                _arr.push(_OpenDB(_GetDBName()));
            }
            _arrDB = await Promise.all(_arr);
        }
        return _arrDB;
    }

    async function RandomReadXData(x) {
        const _out = _CloneStat(_stat);
        _out.requested = x;
        _out.st = Date.now();
        let _sumBytes = 0;
        if (_stat.DBCount > 0) {


            const _MAX_BOUNCH_SIZE_ = 100;/// 最大将这些个请求在一个事务中获取；

            const _arr = [];
            while (x > 0) {
                let _DB = _GetRandomElement(_arrDB);
                let _tableName = _GetRandomName('TBL', 0, _stat.TableCount);

                const _recordNamesToRead = [];
                let _i = _MAX_BOUNCH_SIZE_;
                while (--_i >= 0 && --x >= 0) {
                    _recordNamesToRead.push(_GetRandomName('K', 0, _stat.RecordCount));
                }
                _arr.push(_GetDatas(_DB, _tableName, _recordNamesToRead));
            }

            const _tmp = await Promise.all(_arr);
            _tmp.forEach(v => {
                v.forEach(v2 => {
                    _sumBytes += v2.data.byteLength;
                });
            });
        } else {
            _out.DBCount = 0;
            _out.TableCount = 0;
            _out.RecordCount = 0;
            _out.DataSizeInBytes = 0;
        }
        _out.et = Date.now();
        _out.SumInBytes = _sumBytes;
        return _out;
    }

    function DeleteAllDB() {
        _DeleteAllDB(_stat.DBCount);
        localStorage.removeItem(_CASE_NAME_);
    }

    function Close() {
        _arrDB.forEach(DB => {
            DB.close();
        });
        _arrDB.length = 0;
    }

    return {
        CreateXDBYTableZData,
        Open,
        RandomReadXData,
        DeleteAllDB,
        Close,
    }
}


